package mysql

import (
	"fmt"
	"gin-lite/configs"
	"gin-lite/internal/pkg/errors"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

var repo *Repo

type Repo struct {
	Instance map[string]*gorm.DB
}

func New(conf *configs.Config) *Repo {
	repo = &Repo{
		Instance: make(map[string]*gorm.DB),
	}

	var keys []string
	for key := range conf.Mysql {
		keys = append(keys, key)
	}

	if len(keys) == 0 {
		panic("mysql config err")
	}

	for _, dbConnName := range keys {
		dnName := dbConnName + ".write"
		repo.Instance[dnName] = dbConnect(
			conf.Mysql[dbConnName].Write.Username,
			conf.Mysql[dbConnName].Write.Password,
			conf.Mysql[dbConnName].Write.Host,
			conf.Mysql[dbConnName].Write.Port,
			conf.Mysql[dbConnName].Write.Database,
			conf.Mysql[dbConnName].Write.Charset,
			conf.Mysql[dbConnName].Write.Timeout,
			conf.Mysql[dbConnName].Write.WriteTimeOut,
			conf.Mysql[dbConnName].Write.ReadTimeOut,
			conf.Mysql[dbConnName].Write.MaxOpenConns,
			conf.Mysql[dbConnName].Write.MaxIdleConns,
		)
		dnName = dbConnName + ".read"
		repo.Instance[dnName] = dbConnect(
			conf.Mysql[dbConnName].Read.Username,
			conf.Mysql[dbConnName].Read.Password,
			conf.Mysql[dbConnName].Read.Host,
			conf.Mysql[dbConnName].Read.Port,
			conf.Mysql[dbConnName].Read.Database,
			conf.Mysql[dbConnName].Read.Charset,
			conf.Mysql[dbConnName].Read.Timeout,
			conf.Mysql[dbConnName].Read.WriteTimeOut,
			conf.Mysql[dbConnName].Read.ReadTimeOut,
			conf.Mysql[dbConnName].Read.MaxOpenConns,
			conf.Mysql[dbConnName].Read.MaxIdleConns,
		)
	}

	return repo
}

func (r *Repo) GetConn(keys ...string) *gorm.DB {
	key := "default.write"
	if len(keys) > 0 {
		key = keys[0]
	}

	if r.Instance[key] == nil {
		panic(fmt.Sprintf("%s db connection is not exists", key))
	}

	return r.Instance[key]
}

func (r *Repo) allConnKeys(conf *configs.Config) []string {
	var (
		keys        []string
		allConnKeys []string
	)

	for key := range conf.Mysql {
		keys = append(keys, key)
	}

	for _, dbConnName := range keys {
		allConnKeys = append(allConnKeys, dbConnName+".write")
		allConnKeys = append(allConnKeys, dbConnName+".read")
	}

	return allConnKeys
}

func (r *Repo) Close(conf *configs.Config) error {
	allConnKeys := r.allConnKeys(conf)
	if len(allConnKeys) == 0 {
		return nil
	}

	for _, connKey := range allConnKeys {
		sqlDB, err := r.Instance[connKey].DB()
		if err != nil {
			return errors.New("关闭数据库DB失败: " + err.Error())
		}

		err = sqlDB.Close()
		if err != nil {
			return errors.New(fmt.Sprintf("关闭数据库[%s]失败: %s", connKey, err.Error()))
		}
	}

	return nil
}

func getDsn(username, password, host string, port int, database, charset string, timeout, writeTimeout, readTimeOut int) string {
	return fmt.Sprintf(
		"%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local&timeout=%dms&writeTimeout=%dms&readTimeout=%dms",
		username,
		password,
		host,
		port,
		database,
		charset,
		timeout,
		writeTimeout,
		readTimeOut,
	)
}

func dbConnect(
	username, password, host string,
	port int,
	database, charset string,
	timeout, writeTimeout, readTimeOut, maxOpenConns, maxIdleConns int,
) *gorm.DB {
	dsn := getDsn(username, password, host, port, database, charset, timeout, writeTimeout, readTimeOut)
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true,
		},
		//Logger: logger.Default.LogMode(logger.Info), // 日志配置 TODO
	})

	if err != nil {
		panic(fmt.Sprintf("[db connection failed] Database Dsn: %s\"", dsn))
	}

	db.Set("gorm:table_options", "CHARSET=utf8mb4")

	sqlDB, err := db.DB()
	if err != nil {
		panic(err.Error())
	}

	sqlDB.SetMaxOpenConns(maxOpenConns)
	sqlDB.SetMaxIdleConns(maxIdleConns)
	//sqlDB.SetConnMaxLifetime(time.Minute * 2)

	// use plugin
	err = db.Use(&TracePlugin{})
	if err != nil {
		panic(err.Error())
	}

	return db
}
